#!/usr/bin/python
# -*- coding: utf-8 -*-

'''
Created on 18/03/2010

@author: sebas
'''
import logging.handlers
import os
import pickle
import sys
from pydoc import pager
from cStringIO import StringIO
from optparse import OptionParser
from pickle import HIGHEST_PROTOCOL

from cli import MainMenu, UpdateBookMenu
from model import Item
from model import BookFile
from service import Resolver
from service import Unoconv
from service import EBookConvert 

LOG_FILE = "normalizer.log"
DB_FILE = "authors.db"

class EbookNormalizer(object):
  '''
    En base a algunos patrones heurísticos normaliza nombres y ubicación de archivos de eBooks
  '''
  def __init__(self, from_dir=None, to_dir=None, db_path=None):

    logger.debug("Inicializando [ from-dir = %s, to-dir = %s, db-path = %s ]..." % (from_dir, to_dir, db_path))

    self.from_dir = from_dir
    self.to_dir = to_dir
    self.db_path = db_path
    self.resolver = Resolver(logger)
    self.calibre = EBookConvert()
    self.unoconv = Unoconv(logger)
    self.__db = None
    
    logger.debug ("Leyendo base de datos...")
    self.__get_db()
    logger.debug("Base de datos leida correctamente, un total de %d entradas" % len(self.__get_db()))

  def collect_info(self):
    if self.from_dir is None or not os.path.isdir(self.from_dir):
      raise ValueError("No se puede continuar sin tener seteado correctamente el directorio origen (%s)" % self.from_dir)

    os.path.walk(self.from_dir, self.__collect, {"db": self.__get_db(), "save": lambda db: self.__save_db()})

  def authors(self):
    self.__authors(self.__get_db())

  def processed_authors(self):
    self.__authors(self.__processed())

  def remains_authors(self):
    self.__authors(self.__remains())
  
  def __authors(self, db):
    authors = dict()
    for i in db:
      bf = i.bookfile
      akey = " & ".join(map(lambda x : str(x), bf.book.authors))
      if akey not in authors.keys():
        authors[akey] = list()
      authors[akey].append(bf)

    keys = authors.keys()
    keys.sort()
    
    ofh = StringIO()
    for i, a in enumerate(keys):
      print >> ofh, "%d. %s (%d libros)" % (i, a, len(authors[a]))
    ofh.seek(0)
    pager(ofh.read())

    print "Hay un total de %d autores encontrados" % len(authors)

  def process(self):
    if self.to_dir is None or (os.path.exists(self.to_dir) and not os.path.isdir(self.to_dir)):
      raise ValueError("No se puede continuar sin tener seteado correctamente el directorio destino (%s)" % self.to_dir)
    elif not os.path.exists(self.to_dir):
      os.makedirs(self.to_dir)
    
    db = self.__get_db()
    for i in db:
      if i.get_attribute("edited"):
        source = i.bookfile.location
        if self.resolver.valid_format(source):
          target, opts = self.resolver.epub(i, self.to_dir)
          if not os.path.exists(target):
            logger.info ("Convirtiendo %s a %s (%s)" % (source, target, opts))
            self.calibre.convert(source, target, opts)
          else:
            logger.info ("No se convierte %s a %s, destino existente" % (source, target))
  
  def convert(self):
    db = self.__get_db()
    for i in db:
      if i.get_attribute("edited"):
        source = i.bookfile.location
        ext = self.resolver.extension(source)
        if ext.lower() == "doc":
          target = self.resolver.change_extension(source, "html")
          if not os.path.exists(target):
            status = self.unoconv.convert(source, target)
          else:
            logger.debug("No se convierte '%s', archivo ya existente" % target)
            status = target

          if status is not None:
            i.bookfile.location = target
    self.__save_db()

  def clear_attributes(self):
    for i in self.__get_db():
      i.attributes.clear()
    self.__save_db()

  def __processed(self):
    return filter(lambda x: x.get_attribute("edited"), self.__get_db())

  def __remains(self):
    return filter(lambda x: not x.get_attribute("edited"), self.__get_db())
    
  def processed_books(self):
    self.__books(self.__processed())
      
  def remains_books(self):
    self.__books(self.__remains())
  
  def books(self):
    self.__books(self.__get_db())
    
  def __books(self, db):
    count = 1
    ofh = StringIO()

    for i, item in enumerate(db):
      print >> ofh, "%d. %s" % (i, str(item))
      count += 1

    ofh.seek(0)
    pager(ofh.read())

    print "Hay un total de %d libros encontrados" % len(db)
    
  def update(self, idx):
    db = self.__get_db()
    processed = 0

    if idx is not None:
      i = db[idx]
      state = UpdateBookMenu.State(i, db)
      UpdateBookMenu(state).cmdloop()
      i.attributes["edited"] = True
      processed = 1
    else:
      for i in db:
        if not i.get_attribute(Item.EDITED):
          state = UpdateBookMenu.State(i, db)
          UpdateBookMenu(state).cmdloop()
          if state.action == UpdateBookMenu.State.ABORT:
            break
          i.attributes["edited"] = True
          processed += 1
    
    if processed > 0:
      self.__save_db()
      print "Se procesaron %d libros" % processed
    else:
      print "No se procesaron libros"

  def detail(self, idx):
    db = self.__get_db()
    
    item = db[idx]
    print "\tLibro: %s" % item.bookfile.book.title
    print "\tAutores: %s" % map(lambda x : str(x), item.bookfile.book.authors)
    print "\tArchivo: %s" % item.bookfile.location
    print "\tInfo de google: %s" % "Si" if item.bookfile.book.gbook is not None else "No"

  def __collect(self, arg, dirname, names):
    save = arg["save"]
    db = arg["db"]

    count = 0
    for n in names:
        location = os.path.join(dirname, n)
        title, authors = self.resolver.infer_data(location)

        if title is None:
          logger.debug("No hay matches para '%s'" % n)
          continue
        
        processed = filter(lambda i : i.bookfile.location == location, db)
        if len(processed) > 0:
          logger.info ("No se agrega '%s', libro ya procesado" % location)
          continue
        
        if self.resolver.extension(location).upper() not in self.resolver.ALLOWED_EXTENSIONS:
          logger.info ("No se agrega '%s', formato no soportado" % location)
          continue
        
        if os.path.isdir(location):
          logger.info ("No se agrega '%s', directorios no soportados" % location)
          continue
        
        gbook = self.resolver.gbook(title, authors)
        bookfile = BookFile(self.resolver.book(title, authors, gbook), location)
        logger.info("agregando %s" % str(bookfile))
        db.append(Item(bookfile))
      
        count += 1
        if count > 50:
          count = 0
          save(db)

    if count != 0:
      save(arg["db"])

  def __get_db(self):
    if self.__db is not None:
      return self.__db

    file = None
    try:
      self.__db = list()
      if os.path.exists(self.db_path):
        file = open(self.db_path)
        self.__db = pickle.load(file)
      return self.__db
    finally:
      if file:
        file.close()
  
  def __save_db(self, protocol=HIGHEST_PROTOCOL):
    file = None
    try:
      file = open(self.db_path, "w")
      self.__db.sort()
      pickle.dump(self.__db, file, protocol)
    finally:
      if file:
        file.close()

if __name__ == '__main__':
  description = "%prog Normalizador de eBooks"
  parser = OptionParser(usage="%prog <options>", version="0.1", description=description)
  parser.add_option("-d", "--db-file", dest="db_file", type="string", help="Archivo donde se guardan los datos", metavar="<db-file>")
  options, args = parser.parse_args()

  from_dir = None
  to_dir = None

  global logger
  logger = logging.getLogger("normalizer")
  logger.setLevel(logging.DEBUG)
  formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
  handler = logging.handlers.RotatingFileHandler(LOG_FILE)
  handler.setLevel(logging.DEBUG)
  handler.setFormatter(formatter)
  logger.addHandler(handler)

  if len(args) >= 1:
    if not os.path.exists(args[0]):
      print "No existe %s, imposible continuar" % args[0]           
      sys.exit(1)
    from_dir = args[0]

  if len(args) >= 2:
    to_dir = args[1]

  MainMenu(EbookNormalizer(from_dir, to_dir, options.db_file if options.db_file else DB_FILE)).cmdloop()
